<?php

namespace JfbFcnext;

class ApiClientUtil
{

    public const DefaultSignatureAlgorithm = 'ACS3-HMAC-SHA256';

    /**
     * Hash the raw data with signatureAlgorithm.
     *
     * @param int[]  $raw                hashing data
     * @param string $signatureAlgorithm the autograph method
     *
     * @return array hashed bytes
     */
    public static function hash($raw, $signatureAlgorithm)
    {
        $str = Utils::toString($raw);

        switch ($signatureAlgorithm) {
            case 'ACS3-HMAC-SHA256':
            case 'ACS3-RSA-SHA256':
                $res = hash('sha256', $str, true);
                return Utils::toBytes($res);
            case 'ACS3-HMAC-SM3':
                $res = self::sm3($str);
                return Utils::toBytes(hex2bin($res));
        }

        return [];
    }

    /**
     * Encode raw with base16.
     *
     * @param int[] $raw encoding data
     *
     * @return string encoded string
     */
    public static function hexEncode($raw)
    {
        if (is_array($raw)) {
            $raw = Utils::toString($raw);
        }
        return bin2hex($raw);
    }

    /**
     * Get the authorization.
     *
     * @param Request $request            request params
     * @param string  $signatureAlgorithm the autograph method
     * @param string  $payload            the hashed request
     * @param string  $accesskey          the accessKey string
     * @param string  $accessKeySecret    the accessKeySecret string
     *
     * @return string authorization string
     * @throws \ErrorException
     *
     */
    public static function getAuthorization(\GuzzleHttp\Psr7\Request $request, $signatureAlgorithm, $payload, $accesskey, $accessKeySecret)
    {
        $canonicalURI = $request->getUri()->getPath() ?? '/';
        $query    = $request->getUri()->getQuery() ?: [];
        $method               = strtoupper($request->getMethod());
        $canonicalQueryString = self::getCanonicalQueryString($query);
        $signHeaders          = [];
        foreach ($request->getHeaders() as $k => $v) {
            $k = strtolower($k);
            if (0 === strpos($k, 'x-acs-') || 'host' === $k || 'content-type' === $k) {
                $signHeaders[$k] = $v[0];
            }
        }
        ksort($signHeaders);
        $headers = [];
        foreach ($request->getHeaders() as $k => $v) {
            $k = strtolower($k);
            if (0 === strpos($k, 'x-acs-') || 'host' === $k || 'content-type' === $k) {
                $headers[$k] = trim($v[0]);
            }
        }
        $canonicalHeaderString = '';
        ksort($headers);
        foreach ($headers as $k => $v) {
            $canonicalHeaderString .= $k . ':' . trim(self::filter($v)) . "\n";
        }
        if (empty($canonicalHeaderString)) {
            $canonicalHeaderString = "\n";
        }

        $canonicalRequest = $method . "\n" . $canonicalURI . "\n" . $canonicalQueryString . "\n" .
            $canonicalHeaderString . "\n" . implode(';', array_keys($signHeaders)) . "\n" . $payload;
        $strtosign        = $signatureAlgorithm . "\n" . self::hexEncode(self::hash(Utils::toBytes($canonicalRequest), $signatureAlgorithm));
        $signature        = self::sign($accessKeySecret, $strtosign, $signatureAlgorithm);
        $signature        = self::hexEncode($signature);

        return $signatureAlgorithm .
            ' Credential=' . $accesskey .
            ',SignedHeaders=' . implode(';', array_keys($signHeaders)) .
            ',Signature=' . $signature;
    }

    private static function getCanonicalQueryString($query)
    {
        ksort($query);

        $params = [];
        foreach ($query as $k => $v) {
            if (null === $v) {
                continue;
            }
            $str = rawurlencode($k);
            if ('' !== $v && null !== $v) {
                $str .= '=' . rawurlencode($v);
            } else {
                $str .= '=';
            }
            $params[] = $str;
        }

        return implode('&', $params);
    }

    private static function filter($str)
    {
        return str_replace(["\t", "\n", "\r", "\f"], '', $str);
    }

    /**
     * Get timestamp.
     *
     * @return string the timestamp string
     */
    public static function getTimestamp()
    {
        return gmdate('Y-m-d\\TH:i:s\\Z');
    }

    public static function sign($secret, $str, $algorithm)
    {
        $result = '';
        switch ($algorithm) {
            case 'ACS3-HMAC-SHA256':
                $result = hash_hmac('sha256', $str, $secret, true);
                break;
//            case 'ACS3-HMAC-SM3':
//                $result = self::hmac_sm3($str, $secret, true);
//                break;
//            case 'ACS3-RSA-SHA256':
//                $privateKey = "-----BEGIN RSA PRIVATE KEY-----\n" . $secret . "\n-----END RSA PRIVATE KEY-----";
//                @openssl_sign($str, $result, $privateKey, OPENSSL_ALGO_SHA256);
        }

        return Utils::toBytes($result);
    }
}
